package com.miku.ai.scoring;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.miku.ai.common.ErrorCode;
import com.miku.ai.exception.ThrowUtils;
import com.miku.ai.model.dto.question.QuestionAnswerDTO;
import com.miku.ai.model.dto.question.QuestionContentDTO;
import com.miku.ai.model.entity.App;
import com.miku.ai.model.entity.Question;
import com.miku.ai.model.entity.ScoringResult;
import com.miku.ai.model.entity.UserAnswer;
import com.miku.ai.model.vo.QuestionVO;
import com.miku.ai.model.vo.ScoringResultVO;
import com.miku.ai.service.AppService;
import com.miku.ai.service.QuestionService;
import com.miku.ai.utils.AiUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * AI测评类应用评分策略
 */
@ScoringSrategyConfig(appType = 1,scoringStrategy = 1)
public class AiTestScoringStrategy implements ScoringStrategy {

    @Resource
    private QuestionService questionService;

    @Resource
    private AppService appService;
    @Resource
    private AiUtils aiUtils;

    @Resource
    private RedissonClient redissonClient;

    private static final String AI_ANSWER_LOCK="ai:lock";




    /**
     * 引入本地缓存
     */
    private final Cache<String, String> answerCacheMap =
            Caffeine.newBuilder().initialCapacity(1024)
                    // 缓存5分钟移除
                    .expireAfterAccess(5L, TimeUnit.MINUTES)
                    .build();
    /**Q
     * AI系统提示信息
     */
    private static final String AI_SYSTEM_MESSAGE="你是一位严谨的判题专家，我会给你如下信息：\n" +
            "```\n" +
            "应用名称，\n" +
            "【【【应用描述】】】，\n" +
            "题目和用户回答的列表：格式为 [{\"title\": \"题目\",\"answer\": \"用户回答\"}]\n" +
            "```\n" +
            "\n" +
            "请你根据上述信息，按照以下步骤来对用户进行评价：\n" +
            "1. 要求：需要给出一个明确的评价结果，包括评价名称（尽量简短）和评价描述（尽量详细，大于 200 字）\n" +
            "2. 严格按照下面的 json 格式输出评价名称和评价描述\n" +
            "```\n" +
            "{\"resultName\": \"评价名称\", \"resultDesc\": \"评价描述\"}\n" +
            "```\n" +
            "3. 返回格式必须为 JSON 对象\n";


    /**
     * 拼接用户预设
     * @param app
     * @param questionContentDTOS
     * @param choices
     * @return
     */
    public static String getUserMessage(App app, List<QuestionContentDTO> questionContentDTOS, List<String> choices){
        StringBuilder userMessage =new StringBuilder();
        //拼接应用名称
        userMessage.append(app.getAppName()).append("\n");
        //拼接应用描述
        userMessage.append(app.getAppDesc()).append("\n");
        List<QuestionAnswerDTO> questionAnswerDTOS = new ArrayList<>();
        for (int i = 0; i < questionContentDTOS.size(); i++) {
            QuestionAnswerDTO questionAnswerDto=new QuestionAnswerDTO();
            questionAnswerDto.setTitle(questionContentDTOS.get(i).getTitle());
            questionAnswerDto.setUserAnswer(choices.get(i));
            questionAnswerDTOS.add(questionAnswerDto);
        }
        userMessage.append(JSONUtil.toJsonStr(questionAnswerDTOS));
        return userMessage.toString();
    }

    /**
     * ai生成测评结果
     * @param choices
     * @param app
     * @return
     * @throws Exception
     */
    @Override
    public UserAnswer doScore(List<String> choices, App app) throws Exception {

        //1. 根据id查询到题目和结果信息
        Long appId = app.getId();
        ThrowUtils.throwIf(appId == null, ErrorCode.PARAMS_ERROR);
        //将用户选择转为Json
        String jsonStr = JSONUtil.toJsonStr(choices);
        String cacheKey = setCacheKey(appId, jsonStr);
        //查缓存看是否存在
        String answerJson = answerCacheMap.getIfPresent(cacheKey);
        if (StrUtil.isNotBlank(answerJson)){
            UserAnswer userAnswer = JSONUtil.toBean(answerJson, UserAnswer.class);
            userAnswer.setAppId(appId);
            userAnswer.setAppType(app.getAppType());
            userAnswer.setScoringStrategy(app.getScoringStrategy());
            userAnswer.setChoices(jsonStr);
            return userAnswer;
        }

        //获取锁-给同一个缓存加锁（解决缓存击穿问题
        RLock lock = redissonClient.getLock(AI_ANSWER_LOCK + cacheKey);
        try {
            //竞争锁
            boolean tryLock = lock.tryLock(3, 15, TimeUnit.MINUTES);
            //没有抢到锁，直接返回
            if (!tryLock){
                return null;
            }

            Question question = questionService.getOne(Wrappers.<Question>lambdaQuery().eq(Question::getAppId, appId));
            QuestionVO questionVO = QuestionVO.objToVo(question);
            List<QuestionContentDTO> questionContent = questionVO.getQuestionContent();
            //2. 调用AI系统进行评分
            String userMessage = getUserMessage(app, questionContent, choices);
            String systemMessage=AI_SYSTEM_MESSAGE;
            String result = aiUtils.doSyncUnStableRequest(systemMessage, userMessage);
            //3. 解析AI系统返回结果-结果处理
            int start=result.indexOf("{");
            int end=result.lastIndexOf("}");
            String resultJson=result.substring(start,end+1);
            //AI生成成功，存入缓存
            answerCacheMap.put(cacheKey,resultJson);

            UserAnswer userAnswer=JSONUtil.toBean(resultJson, UserAnswer.class);
            userAnswer.setAppId(appId);
            userAnswer.setAppType(app.getAppType());
            userAnswer.setScoringStrategy(app.getScoringStrategy());
            userAnswer.setChoices(jsonStr);
            return userAnswer;
        }finally {
            //如果锁不为空，且被锁住了
            if (lock!=null && lock.isLocked()){
                //是本人才能解锁
                if (lock.isHeldByCurrentThread()){
                    lock.unlock();
                }
            }
        }
    }

    /**
     * 设置缓存key
     * @param appId
     * @param choices
     * @return
     */
    public String setCacheKey(Long appId,String choices){
        //把选择进行hash算法，减少内存
         choices = DigestUtil.md5Hex(choices);
         return appId+":"+choices;
    }
}
